package com.admin.system.interceptor;

import com.admin.base.BaseQuery;
import com.admin.base.Page;
import com.admin.base.Paging;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

public class PageExecutor implements Executor {

  private Executor executor;

  public PageExecutor(Executor executor) {
    this.executor = executor;
  }

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    return executor.update(ms, parameter);
  }

  @SuppressWarnings("rawtypes")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
      CacheKey cacheKey, BoundSql boundSql) throws SQLException {
    RowBounds rb = rowBounds;
    if (isPaging(ms)) {
      if (parameter instanceof BaseQuery) {
        BaseQuery query = (BaseQuery) parameter;
        rb = new RowBounds(query.getStartRow(), query.getPageSize());
      }
    }
    List<E> rows = executor.query(ms, parameter, rb, resultHandler, cacheKey, boundSql);
    return pageResolver(rows, ms, parameter);
  }

  /**
   * 修改返回值类型
   */
  private <E> List<E> pageResolver(List<E> rows, MappedStatement ms, Object parameter) {
    // 如果需要分页查询，修改返回类型为Page对象
    if (isPaging(ms)) {
      int count = getCount(ms, parameter);
      if (parameter instanceof BaseQuery) {
        BaseQuery query = (BaseQuery) parameter;
        int offset = query.getStartRow();
        int pagesize = query.getPageNumber();
        return new Page<E>(offset / pagesize + 1, pagesize, count, rows);
      }
    }
    return rows;
  }

  /**
   * 获取总数
   */
  private int getCount(MappedStatement ms, Object parameter) {
    BoundSql bsql = ms.getBoundSql(parameter);
    String sql = bsql.getSql();
    String countSql = getCountSql(sql);
    Connection connection = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;
    try {
      connection = ms.getConfiguration().getEnvironment().getDataSource().getConnection();
      stmt = connection.prepareStatement(countSql);
      setParameters(stmt, ms, bsql, parameter);
      rs = stmt.executeQuery();
      if (rs.next()) {
        return rs.getInt(1);
      }
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        if (connection != null && !connection.isClosed()) {
          connection.close();
        }
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
    return 0;
  }

  @SuppressWarnings("unchecked")
  private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
      Object parameterObject) throws SQLException {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      Configuration configuration = mappedStatement.getConfiguration();
      TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
      MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else if (boundSql.hasAdditionalParameter(propertyName)) {
            value = boundSql.getAdditionalParameter(propertyName);
          } else {
            value = metaObject == null ? null : metaObject.getValue(propertyName);
          }
          @SuppressWarnings("rawtypes")
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          if (typeHandler == null) {
            throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName
                + " of statement " + mappedStatement.getId());
          }
          typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
        }
      }
    }
  }

  private String getCountSql(String sql) {
    String countHql = " SELECT count(*) " + removeSelect(removeOrders(sql));

    return countHql;
  }

  protected String removeOrders(String sql) {
    Pattern p = Pattern.compile("ORDER\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
    Matcher m = p.matcher(sql);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
      m.appendReplacement(sb, "");
    }
    m.appendTail(sb);
    return sb.toString();
  }

  /**
   * 去除sql语句中select子句
   * @param hql
   * @return
   */
  private static String removeSelect(String hql) {
    int beginPos = hql.toLowerCase().indexOf("from");
    if (beginPos < 0) {
      throw new IllegalArgumentException(" hql : " + hql + " must has a keyword 'from'");
    }
    return hql.substring(beginPos);
  }

  /**
   * 判断该方法是否分页
   */
  private boolean isPaging(MappedStatement statement) {
    try {
      Class<?> classType = Class.forName(statement.getId().substring(0, statement.getId().lastIndexOf(".")));
      String methodName = statement.getId().substring(statement.getId().lastIndexOf(".") + 1, statement.getId().length());
      //遍历方法上是否添加Paging注解，判断是否分页
      for (Method method : classType.getDeclaredMethods()) {
        if (method.isAnnotationPresent(Paging.class) && methodName.equals(method.getName())) {
          return true;
        }
      }
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
      return false;
    }
    return false;
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
      throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return query(ms, parameter, rowBounds, resultHandler,
        executor.createCacheKey(ms, parameter, rowBounds, boundSql), boundSql);
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return executor.flushStatements();
  }

  @Override
  public void commit(boolean required) throws SQLException {
    executor.commit(required);
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    executor.rollback(required);
  }

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return executor.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  }

  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    return executor.isCached(ms, key);
  }

  @Override
  public void clearLocalCache() {
    executor.clearLocalCache();
  }

  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key,
      Class<?> targetType) {
    executor.deferLoad(ms, resultObject, property, key, targetType);
  }

  @Override
  public Transaction getTransaction() {
    return executor.getTransaction();
  }

  @Override
  public void close(boolean forceRollback) {
    executor.close(forceRollback);
  }

  @Override
  public boolean isClosed() {
    return executor.isClosed();
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    return executor.queryCursor(ms, parameter, rowBounds);
  }

  @Override
  public void setExecutorWrapper(Executor executor) {
    this.executor = executor;
  }

}